/*
 * Copyright (c) 2006-2007 Zsolt Szasz <zsolt at lorecraft dot com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.lorecraft.routes;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class RouteImpl implements Route {

	private static final String ACTION = "action";
	private final String template;
	private final String namespace;
	private final Map<String, String> defaults;
	private Element root;

	public RouteImpl(String template, String namespace, String[] defaults) {
		this.template = template;
		this.namespace = namespace;
		this.defaults = Utils.convertToMap(defaults);
		this.root = Element.NULL;
		parseTemplate();
	}

	private void parseTemplate() {
		String[] templateElements = Utils.split(template);
		if (templateElements.length == 0) {
			return;
		}
		Element lastElement = null;
		for (String s : templateElements) {
			if (lastElement == null){
				lastElement = RouteFactory.get().createElement(s);
				root = lastElement;
			} else {
				lastElement = lastElement.link(RouteFactory.get().createElement(s));
			}
		}
		lastElement.link(Element.NULL);
	}

	public RouteMapping map(String path) {
		RouteMapping routeMapping = new RouteMapping(namespace);
		// put all default parameters in the result, any explicitly passed
		// parameter will just overwrite the default one
		for (String key : defaults.keySet()) {
			routeMapping.getParams().put(key, new String[] { defaults.get(key) });
		}
		// strip the query string, if any
		String requiredPath = (path.indexOf('?') > 0 ? path.substring(0, path.indexOf('?')) : path);
		String[] elementStrings = Utils.split(requiredPath);

		// start processing
		ProcessingContext context = new ProcessingContext(elementStrings, routeMapping);
		root.map(context);
		if (context.hasMoreElements()) {
			routeMapping.markInvalid();
		}
		return routeMapping;
	}

	public String getNamespace() {
		return namespace;
	}

	public String getPath(String namespace, String action, String... params) {
		if (!this.namespace.equals(namespace)) {
			return null;
		}
		// check if a default action is specified and matches requested action
		if (defaults.containsKey(ACTION) && !action.equals(defaults.get(ACTION))) {
			return null;
		}

		StringBuilder builder = new StringBuilder();
		Map<String, String> mergedParams = new HashMap<String, String>(defaults);
		mergedParams.putAll(Utils.convertToMap(params));
		mergedParams.put(ACTION, action);
		List<String> undefineds = new ArrayList<String>();
		try {
			root.getPath(builder, mergedParams, undefineds);
		} catch (IllegalArgumentException e) {
			return null;
		}

		// append any remaining parameters as query parameters
		mergedParams.remove(ACTION);
		if (builder.length() == 0) {
			builder.append('/');
		}
		if (mergedParams.size() > 0) {
			builder.append("?");
			boolean first = true;
			for (Map.Entry<String, String> paramEntry : mergedParams.entrySet()) {
				if (!first) {
					builder.append("&");
				} else {
					first = false;
				}
				builder.append(paramEntry.getKey());
				builder.append("=");
				final String param = paramEntry.getValue();
				builder.append(encode(param));
			}
		}
		String path = builder.toString();
		return path;
	}

	private String encode(final String param) {
		String result;
		try {
			result = URLEncoder.encode(param, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// no UTF-8 support?? unlikely
			e.printStackTrace();
			result = URLEncoder.encode(param);
		}

		return result;
	}
}
